home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
MacHack 2000
/
MacHack 2000.toast
/
pc
/
Presentations
/
Closures, Observers, Properties
/
Cop Notes
next >
Wrap
Text File
|
2000-06-22
|
4KB
|
138 lines
Model and View Design
The model does the work; views display and control the model
Ideally, the model doesn't know about the views.
Views can know about the model, but the less they know, the more reusable they are.
Today's goal: provide objects which connect the model to the views
while minimizing the dependencies between them.
Closures
The first problem is easy: how do we connect a pushbutton to a method
of the model?
First try: give the button a function pointer to call:
void (*toCall)()
That works well from the point of view of the button class, but is difficult
to connect to a model, which likely provides a member function, not a
plain function. So how do we make the button call model.Method()?
Second try: instead of a function pointer, use an object with an operator()
<Code>
That works, but it's not very general -- it can only be used for
one method of the Model class. We can allow it to call an arbitrary
method by using a pointer to a member function:
<Code>
But we're still wedded to a particular Model class. We'd like
the Closure to be able to call a method of any class. So we'll
remove the dependencies on the model class in stages. We can
remove one dependency in the data by storing a void* instead
of a Model*:
<Code>
We'd like to do the same for the method member. But a pointer to a member
function does not convert to a void*. Instead, we'll convert it to
a pointer to a member function of a class we'll make up, called "Generic."
It's safe to do that as long as we convert it back to the original
type before calling it:
<Code>
Now the data members don't depend on the Model class. But the
member functions still do. But we can make Model a template
parameter to get around that:
<Code>
Now we have another problem: there's one operator() for each Model type.
And if the one we call isn't the one the Closure was constructed with,
we'll probably crash. So we'll do better to record which function to
call when the Closure is constructed:
<Code>
That's the one that works. But we'll throw in one more change -- we'll
make Closure a template based on the type of function pointer it
resembles. To do that, we'll declare a general template without a
definition, and provide partial specializations for different lengths
of the parameter list:
<Code>
Observables
Next, consider a view which needs to be informed of events in the
model. As an example, take a standard file dialog, which responds
to the mounting of a new disk. The model shouldn't simply call
a view through a closure -- it shouldn't even know whether it has
a view that needs to be called.
Instead, we'd like to think of the views as observing events in
the model. When an event occurs in the model, it broadcasts
a message to all observers. To do this, we give the model
a class which holds a list of observers. Again, we template
the class on the signature of the message the model sends.
<Code>
Properties
Now consider a view that both reads and changes a value. This comes
up particularly often in AppleScript, and they even use the same name
for this sort of connection that I will: a property.
A good example of a property is the "zoomed" state of a window.
From AppleScript, you can ask whether a window is zoomed, and you can
also tell a window to be zoomed or not. The property acts like a
boolean variable, but there's often not really a variable there.
So our property class will act like a reference to a variable.
<Code>
Observable Properties
Finally, the majority of user-interface objects need three kinds
of communication with the model. They need to read the current
state, make changes to the state, and be notified when the state
changes. To cover all three bases, we combine the Property and
Observable templates.
<Code>